import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

plt.rcParams['font.sans-serif'] = ['SimHei']     # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False       # 正常显示负号

# 加载鸢尾花数据
data = load_iris()
X = data.data[:, :2]  # 使用前两个特征
y = data.target

# 仅使用类别0（Setosa）和类别1（Versicolor）
X = X[y != 2]
y = y[y != 2]

# 标签变为 -1 和 +1
y = np.where(y == 0, -1, 1)

# 手写感知机类
class Perceptron:
    def __init__(self, lr=0.01, n_iters=1000):
        self.lr = lr              #学习率（learning_rate），即\eta
        self.n_iters = n_iters    #训练轮次
        self.weights = None       #权重项，即a_1和a_2等
        self.bias = None          #偏置项，即b

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0

        for _ in range(self.n_iters):
            for xi, yi in zip(X, y):
                linear_output = np.dot(xi, self.weights) + self.bias
                # 这是a_1 * x_1 + a_2 * x_2 + b
                y_pred = np.sign(linear_output)
                #sign为激活函数，括号内大于等于0输出+1，小于0输出-1，在此处取不取都不影响结果
                if yi * y_pred <= 0:  # 错误分类
                    self.weights += self.lr * yi * xi
                    self.bias += self.lr * yi

    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        return np.sign(linear_output)

# 初始化并训练模型
model = Perceptron(lr=0.1, n_iters=1000)
model.fit(X, y)

# 预测与准确率
y_pred = model.predict(X)
accuracy = np.mean(y_pred == y)
print(f"准确率: {accuracy * 100:.2f}%")

# 绘图函数：决策边界
def plot_decision_boundary(X, y, model):
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                         np.linspace(y_min, y_max, 200))
    grid = np.c_[xx.ravel(), yy.ravel()]
    Z = model.predict(grid).reshape(xx.shape)

    plt.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
    plt.xlabel("Sepal length")
    plt.ylabel("Sepal width")
    plt.title("感知机决策边界 (Setosa vs Versicolor)")
    plt.show()

plot_decision_boundary(X, y, model)
